/*****************************************************************
Mix3D WebGL Game Engine
Copyright (c) fantasy ( fantasy.x2x@gmail.com )
Listens:
Distributed under the Boost Software License, Version 1.0.
http://www.boost.org/LICENSE_1_0.txt
 ****************************************************************/
/**
 * @Desc    : buffer
 * @Author  : fantasy(fantasy.x2x@gmail.com)
 */
//////////////////////////////////////////////////////////////////
if(typeof(Mix3D) === 'undefined')
{
  Mix3D = {};
};
(function(Mix3D){
//////////////////////////////////////////////////////////////////
//vertex attribute type
Mix3D.VAT_Float1 = 0;
Mix3D.VAT_Float2 = 1;
Mix3D.VAT_Float3 = 2;
Mix3D.VAT_Float4 = 4;
Mix3D.VTA_FloatSize = [ 1, 2, 3, 4];
//vertex buffer usage
Mix3D.VBU_Static = 0;
Mix3D.VBU_Dynamic = 1;
//////////////////////////////////////////////////////////////////
//
//    vertex Buffer
//
//////////////////////////////////////////////////////////////////
Mix3D.VABuffer = function( vat, vtxNum, usage )
{
  if(vtxNum===undefined)vtxNum=0;
  this.vat = vat;
  this.usage = usage!==undefined?usage:Mix3D.VBU_Static;
  this.elementNum = vtxNum;
  this.elementSize = Mix3D.VTA_FloatSize[this.vat];
  Mix3D.FloatArray.call(this, vtxNum * this.elementSize);
  
  this.isDirty = true;
  this.vec3 = new Mix3D.Vector3();
  this.vec4 = new Mix3D.Vector4();
};
Mix3D.protoCopy(Mix3D.FloatArray, Mix3D.VABuffer);

Mix3D.VABuffer.prototype.constructor = Mix3D.VABuffer;
Mix3D.VABuffer.prototype.getNum = function(){ return this.elementNum; };
Mix3D.VABuffer.prototype.getElementSize = function() { return this.elementSize; };

Mix3D.VABuffer.prototype.resetElementNum = function(elementNum, isZero)
{
  if(elementNum === undefined)elementNum = 0;
  var i, oldSize = this.FA.length;
  var newSize = this.elementNum * this.elementSize;
  
  if(this.elementNum !== elementNum)
  {
    this.elementNum = elementNum;
    if(newSize > oldSize)
    {
      for( i=oldSize; i<newSize; ++i)
      {
        this.FA.push(0.0);
      }
    }
  }
  
  if(isZero)
  {
    var len = newSize>oldSize?oldSize:newSize;
    for( i=0; i<len; ++i)
    {
      this.FA[i] = 0.0;
    }
  }
};

Mix3D.VABuffer.prototype.copyElement = function( idx, other, otherIdx)
{
  var pos1 = this.elementSize*idx;
  var pos2 = this.elementSize*otherIdx;
  for( var i=0; i<this.ElementSize; ++i)
  {
    this.FA[pos1+i] = other.FA[pos2+i];
  }
};

Mix3D.VABuffer.prototype.add = function(v)
{
  var i, len = this.elementSize;
  for( i=0; i<len; ++i)
  { 
    this.FA.push(v.FA[i]); 
  }
  ++this.elementNum;
  this.isDirty = true;
  return this;
};

Mix3D.VABuffer.prototype.set = function( idx, v ) 
{
  if(idx>=0 && idx<this.elementNum)
  {
    var pos = idx*this.elementSize;
    var len = Mix3D.Math.Min(this.elementSize, v.getArraySize());
    for( var i=0;i<len; ++i)
    {
      this.FA[pos+i] = v.FA[i];
    }
    this.isDirty = true;
  }
  else
  {
    Mix3D.error("the idx invalid");
  }
  return this;
};

Mix3D.VABuffer.prototype.get = function( idx, v)
{ 
  if(idx>=0 && idx<this.elementNum)
  {
    if(v===undefined)v=new Mix3D.FloatArray(this.elementSize);
    var pos = idx*this.elementSize;
    var len = Mix3D.Math.Min(this.elementSize, v.getArraySize());
    for( var i=0;i<len; ++i)
    {
      v.FA[i] = this.FA[pos+i];
    }
  }
  else
  {
    Mix3D.error("the idx invalid");
  }
  return v;
};

Mix3D.VABuffer.prototype.setVec2 = function( idx, f1, f2)
{
  if(this.elementSize >=2 && idx>=0 && idx<this.elementNum)
  {
    var pos = idx*this.elementSize;
    this.FA[pos] = f1;
    this.FA[pos+1] = f2;
    this.isDirty = true;
  }
  else
  {
    Mix3D.error("VABuffer:setVec2 - inner error");
  }
  return this;
}; 

Mix3D.VABuffer.prototype.setVec3 = function( idx, f1, f2, f3)
{
  if(this.elementSize >=3 && idx>=0 && idx<this.elementNum)
  {
    var pos = idx*this.elementSize;
    this.FA[pos] = f1;
    this.FA[pos+1] = f2;
    this.FA[pos+2] = f3;
    this.isDirty = true;
  }
  else
  {
    Mix3D.error("VABuffer:setVec3 - inner error");
  }
  return this;
};

Mix3D.VABuffer.prototype.setVec4 = function( idx, f1, f2, f3, f4)
{
  if(this.elementSize >=4 && idx>=0 && idx<this.elementNum)
  {
    var pos = idx*this.elementSize;
    this.FA[pos] = f1;
    this.FA[pos+1] = f2;
    this.FA[pos+2] = f3;
    this.FA[pos+3] = f4;
    this.isDirty = true;
  }
  else
  {
    Mix3D.error("VABuffer:setVec4 - inner error");
  }
  return this;
};

Mix3D.VABuffer.prototype.getVec2 = function( idx, vec2 )
{
  if(idx < this.elementNum)
  {
    var pos = idx*this.elementSize;
    vec2.FA[0] = this.FA[pos];
    vec2.FA[1] = this.FA[pos+1];
  }
  else
  {
    Mix3D.error("VABuffer:getVec2 - idx invalid");
  }
  return vec2;
};

Mix3D.VABuffer.prototype.getVec3 = function( idx, vec3 )
{ 
  if(vec3===undefined)vec3 = this.vec3;
  if(idx < this.elementNum)
  {
    var pos = idx*this.elementSize;
    vec3.FA[0] = this.FA[pos];
    vec3.FA[1] = this.FA[pos+1];
    vec3.FA[2] = this.FA[pos+2];
  }
  else
  {
    Mix3D.error("VABuffer : getVec3 - idx invalid");
  }
  return vec3;
};

Mix3D.VABuffer.prototype.getVec4 = function( idx, vec4 )
{
  if(vec4===undefined)vec4 = this.vec4;
  if(idx < this.elementNum)
  {
    var pos = idx*this.elementSize;
    vec4.FA[0] = this.FA[pos];
    vec4.FA[1] = this.FA[pos+1];
    vec4.FA[2] = this.FA[pos+2];
    vec4.FA[3] = this.FA[pos+3];
  }
  else
  {
    Mix3D.error("VABuffer : getVec4 - idx invalid");
  }
  return vec4;
};

Mix3D.VABuffer.prototype.normalize = function()
{
  var i,j, pos, vtxNum = this.getNum();
  var lenSqr, invLen;
  var v;
  for( i=0, pos=0; i<vtxNum; ++i, pos += this.elementSize)
  {
    lenSqr = 0;
    for( j=0; j<this.elementSize; ++j)
    {
      v = this.FA[pos+j];
      lenSqr += v*v;
    }
    invLen = 1.0/Math.sqrt(lenSqr);
    for( j=0; j<this.elementSize; ++j)
    {
      this.FA[pos+j] *= invLen;
    }
  }
  this.isDirty = true;
  return this;
};

Mix3D.VABuffer.prototype.applyTransform = function( mat )
{
  var pos=0, i, v;
  var len = this.getNum();
  if(this.vat === Mix3D.VAT_Float3)
  {
    v = new Mix3D.Vector3();
    for( i=0; i<len; ++i, pos+=3)
    {
      v.FA[0] = this.FA[pos];
      v.FA[1] = this.FA[pos+1];
      v.FA[2] = this.FA[pos+2];
      mat.mulVec3(v);
      this.FA[pos] = v.FA[0];
      this.FA[pos+1] = v.FA[1];
      this.FA[pos+2] = v.FA[2];
    }
    delete v;
  }
  else if( this.vat === Mix3D.VAT_Float4)
  {
    v = new Mix3D.Vector4();
    for( i=0; i<len; ++i, pos+=4)
    {
      v.FA[0] = this.FA[pos];
      v.FA[1] = this.FA[pos+1];
      v.FA[2] = this.FA[pos+2];
      v.FA[3] = this.FA[pos+4];
      mat.mulVec4(v);
      this.FA[pos] = v.FA[0];
      this.FA[pos+1] = v.FA[1];
      this.FA[pos+2] = v.FA[2];
      this.FA[pos+3] = v.FA[3];
    }
    delete v;
  }
  this.isDirty = true;
};

Mix3D.VABuffer.prototype.needUpdates = function( is )
{
  this.isDirty = is!==undefined?is:true;
  return this;
};
//////////////////////////////////////////////////////////////////
//
//    Index Buffer
//
//////////////////////////////////////////////////////////////////
Mix3D.IndexBuffer = function(num)
{
  this.isIndexU32 = num>65535?true:false;
  if(this.isIndexU32)
    this.FA = new Uint32Array(num);
  else
    this.FA = new Uint16Array(num);
    
  this.triangle = [-1, -1, -1];
};

Mix3D.IndexBuffer.prototype.constructor = Mix3D.IndexBuffer;

Mix3D.IndexBuffer.prototype.getArraySize = function() 
{ 
  return this.FA.length; 
};

Mix3D.IndexBuffer.prototype.getTriangleNum = function()
{
  return this.FA.length/3;
};

Mix3D.IndexBuffer.prototype.copy = function( other )
{
  for(var i=0, len=this.FA.length; i<len; ++i)
    this.FA[i] = other.FA[i];
  return this;
}; 

Mix3D.IndexBuffer.prototype.toArray = function( outArray, offset)
{
  if(outArray === undefined) 
  {
    outArray = [];
    offset = 0;
  }
  if(offset===undefined)offset=0;
  for(var i=0,len=this.FA.length;i<len;++i)
  {
    outArray[offset++]=this.FA[i];
  }
  return outArray;
};

Mix3D.IndexBuffer.prototype.fromArray = function( inArray, offset)
{
  var i, len = this.FA.length;
  if(offset===undefined)offset=0;
  if(inArray && inArray.length - offset>=len)
  {
    for(i=0; i<len; ++i)
    {
      this.FA[i] = inArray[offset++];
    }
  }
  return this;
};

Mix3D.IndexBuffer.prototype.toStr = function( outString )
{
  outString = this.FA.toString();
  return outString;
};

Mix3D.IndexBuffer.prototype.addTri = function( a, b, c )
{
  this.FA.push(a,b,c);
  return this;
};

Mix3D.IndexBuffer.prototype.addQuad = function( a, b, c, d )
{
  this.FA.push(a, b, d);
  this.FA.push(b, c, d);
  return this;
};

Mix3D.IndexBuffer.prototype.addTriEdge = function( a, b, c)
{
  this.FA.push(a, b, a, c, b, c)
  return this;
};

Mix3D.IndexBuffer.prototype.addQuadEdge = function( a, b, c, d )
{
  this.FA.push( a, b, a, d, b, c, c, d);
  return this;
};

Mix3D.IndexBuffer.prototype.getTriangle = function( idx )
{
  var pos = idx*3;
  this.triangle[0] = this.FA[pos];
  this.triangle[1] = this.FA[pos+1];
  this.triangle[2] = this.FA[pos+2];
  return this.triangle;
};
//////////////////////////////////////////////////////////////////
//
//    vertices
//
//////////////////////////////////////////////////////////////////
Mix3D.VertexAttrbs = function()
{
  this.buffers={};
};

Mix3D.VertexAttrbs.prototype.constructor = Mix3D.VertexAttrbs;

Mix3D.VertexAttrbs.prototype.createAttrBuffer = function( name, type, vtxNum )
{
  var buf = new Mix3D.VABuffer(type, vtxNum);  
  this.buffers[name] = { vta:type, buffer:buf};
  return buf;
};

Mix3D.VertexAttrbs.prototype.getAttrBuffer = function( name )
{
  var item = this.buffers[name];
  if(item)
    return item.buffer;
  return undefined;
};

Mix3D.VertexAttrbs.prototype.hasAttrBuffer = function( name )
{
  return this.buffers[name]!==undefined;
};

Mix3D.VertexAttrbs.prototype.createPosBuffer = function( vtxNum )
{
  return this.createAttrBuffer("position", Mix3D.VAT_Float3, vtxNum);
};

Mix3D.VertexAttrbs.prototype.createNormalBuffer = function( )
{
  var posBuf = this.buffers["position"];
  if(posBuf)
  {
    return this.createAttrBuffer("normal", Mix3D.VAT_Float3, posBuf.getNum());
  }
  return undefined;
};

Mix3D.VertexAttrbs.prototype.createTangentBuffer = function( )
{
  var posBuf = this.buffers["position"];
  if(posBuf)
  {
    return this.createAttrBuffer("tangent", Mix3D.VAT_Float4, posBuf.getNum());
  }
  return undefined;
};

Mix3D.VertexAttrbs.prototype.createColorBuffer = function( )
{
  var posBuf = this.buffers["position"];
  if(posBuf)
  {
    return this.createAttrBuffer("color", Mix3D.VAT_Float3, posBuf.getNum());// float3
  }
  return undefined;
};

Mix3D.VertexAttrbs.prototype.createUVBuffer = function( idx )
{
  var posBuf = this.buffers["position"];
  if(posBuf)
  {
    var name = "uv";
    if(idx!==undefined && idx>0)name += idx;
    return this.createAttrBuffer(name, Mix3D.VAT_Float2, posBuf.getNum()); // float2
  }
  return undefined;
};

Mix3D.VertexAttrbs.prototype.createBlendWeightBuffer = function( )
{
  var posBuf = this.buffers["position"];
  if(posBuf)
  {
    return this.createAttrBuffer("blendWeight", Mix3D.VAT_Float4, posBuf.getNum());
  }
  return undefined;
};

Mix3D.VertexAttrbs.prototype.createBlendIndexBuffer = function( )
{
  var posBuf = this.buffers["position"];
  if(posBuf)
  {
    return this.createAttrBuffer("blendIndex", Mix3D.VAT_Float4, posBuf.getNum());
  }
  return undefined;
};

Mix3D.VertexAttrbs.prototype.getPosBuffer = function()
{
  var buf = this.getAttrBuffer("position");
  if(!buf)buf = this.createPosBuffer(0);
  return buf;
};

Mix3D.VertexAttrbs.prototype.getNormalBuffer = function()
{
  var buf = this.getAttrBuffer("normal");
  if(!buf)buf = this.createNormalBuffer();
  return buf;
};

Mix3D.VertexAttrbs.prototype.getTangentBuffer = function()
{
  var buf = this.getAttrBuffer("tangent");
  if(!buf)buf = this.createTangentBuffer();
  return buf;
};

Mix3D.VertexAttrbs.prototype.getColorBuffer = function()
{
  var buf = this.getAttrBuffer("color");
  if(!buf)buf = this.createColorBuffer();
  return buf;
};

Mix3D.VertexAttrbs.prototype.getUVBuffer = function(idx)
{
  var name = "uv";
  if(idx !==undefined && idx>0)name += idx;
  var buf = this.getAttrBuffer(name);
  if(!buf)buf = this.createUVBuffer(idx);
  return buf;
};

Mix3D.VertexAttrbs.prototype.getBlendWeightBuffer = function()
{
  var buf = this.getAttrBuffer("blendWeight");
  if(!buf)buf = this.createBlendWeight();
  return buf;
};

Mix3D.VertexAttrbs.prototype.getBlendIndexBuffer = function()
{
  var buf = this.getAttrBuffer("blendIndex");
  if(!buf)buf = this.createBlendIndexBuffer();
  return buf;
};

Mix3D.VertexAttrbs.prototype.hasPos = function()
{
  return this.hasAttrBuffer("position");
};

Mix3D.VertexAttrbs.prototype.hasNormal = function()
{
  return this.hasAttrBuffer("normal");
};

Mix3D.VertexAttrbs.prototype.hasTangent = function()
{
  return this.hasAttrBuffer("tangent");
};

Mix3D.VertexAttrbs.prototype.hasColor = function()
{
  return this.hasAttrBuffer("color");
};

Mix3D.VertexAttrbs.prototype.hasUV = function(idx)
{
  var name = "uv";
  if(idx !==undefined && idx>0)name += idx;
  return this.hasAttrBuffer(name);
};

Mix3D.VertexAttrbs.prototype.hasBlendWeight = function()
{
  return this.hasAttrBuffer("blendWeight");
};

Mix3D.VertexAttrbs.prototype.hasBlendIndex = function()
{
  return this.hasAttrBuffer("blendIndex");
};


//////////////////////////////////////////////////////////////////
//
//  RenderVas
//
//////////////////////////////////////////////////////////////////
Mix3D.RenderVAS = function(basePartID)
{
  var baseID = basePartID;
  this.vas = new Mix3D.VertexAttrbs();
  this.fvas = new Mix3D.VertexAttrbs();
  this.triangleBuffer = new Mix3D.IndexBuffer(0);
  this.edgeBuffer = new Mix3D.IndexBuffer(0);
  this.facesArray = [];
  this.face3 = [];
  this.face4 = [];
  this.vtxCount = 0;
  this.shareVtxHash = {};
  this.mavGroups = [];
  
  this.getID() = function() { return baseID; };
};

Mix3D.RenderVAS.prototype.constructor = Mix3D.RenderVAS;

Mix3D.RenderVAS.prototype.getFaceArray = function() { return this.facesArray; };
Mix3D.RenderVAS.prototype.getVAS = function() { return this.vas; };
Mix3D.RenderVAS.prototype.getFVAS = function() { return this.fvas; };

//vertex attr
Mix3D.RenderVAS.prototype.addMvaBuffer = function( attrName, vatType, material )
{
  var mva = this.getMVA(material);
  if(mva)
  {
    return mva.vas.createAttrBuffer(attrName, type, this.vtxCount );
  }
  return undefined;
};
Mix3D.RenderVAS.prototype.addMvaVec1Buffer = function( attrName, material )
{
  return this.addMvaBuffer(attrName, Mix3D.VAT_Float1, material );
};
Mix3D.RenderVAS.prototype.addMvaVec2Buffer = function( attrName, material )
{
  return this.addMvaBuffer(attrName, Mix3D.VAT_Float2, material );
};
Mix3D.RenderVAS.prototype.addMvaVec3Buffer = function( attrName, material )
{
  return this.addMvaBuffer(attrName, Mix3D.VAT_Float3, material );
};
Mix3D.RenderVAS.prototype.addMvaVec4Buffer = function( attrName, material )
{
  return this.addMvaBuffer(attrName, Mix3D.VAT_Float4, material );
};
Mix3D.RenderVAS.prototype.getMvaBuffer = function( attrName, material )
{
  var mva = this.getMVA(material);
  if(mva)
  {
    return mva.vas.getAttrBuffer(item);
  }
  return undefined;
};
Mix3D.RenderVAS.prototype.setMVA = function( attrName, idx, v, material )
{
  var buff = undefined;
  var mva = this.getMVA(material);
  if(mva)
  {
    buff = mva.vas.getAttrBuffer(attrName);
  }
  if(buff)
  {
    buff.set(idx, v);
  }
  return this;
};
//add face attr
Mix3D.RenderVAS.prototype.addMfaBuffer = function( attrName, vatType, material )
{
  var mva = this.getMVA(material);
  if(mva)
  {
    return mva.fvas.createAttrBuffer(attrName, vatType, this.vtxCount );
  }
  return undefined;
};
Mix3D.RenderVAS.prototype.addMfaVec1Buffer = function( attrName, material )
{
  return this.addMfaBuffer(attrName, Mix3D.VAT_Float1, material );
};
Mix3D.RenderVAS.prototype.addMfaVec2Buffer = function( attrName )
{
  return this.addMfaBuffer(attrName, Mix3D.VAT_Float2, material );
};
Mix3D.RenderVAS.prototype.addMfaVec3Buffer = function( attrName )
{
  return this.addMfaBuffer(attrName, Mix3D.VAT_Float3, material );
};
Mix3D.RenderVAS.prototype.addMfaVec4 = function( attrName )
{
  return this.addMfaBuffer(attrName, Mix3D.VAT_Float4, material );
};
Mix3D.RenderVAS.prototype.getMfaBuffer = function( attrName, material )
{
  var mva = this.getMVA(material);
  if(mva)return mva.fvas.getAttrBuffer(attrName);
};
/*Mix3D.RenderVAS.prototype.setMFA = function( item, face, v, material )
{
  if(!face.shareIdxs)return this;
  var idxArray = face.shareIdxs;
  var buff = undefined;

  var mva = this.getMVA(material);
  if(mva)buff = mva.fvas.getAttrBuffer(item);
  
  if(buff)
  {
    for( var i=0,len=idxArray.length; i<len; ++i)
    {
      buff.set(idxArray[i], v);
    }
  }
  return this;
};*/

Mix3D.RenderVAS.prototype.getMVA = function(material)
{
  var i, len;
  for( i=0,len=this.mavGroups.length; i<len; ++i)
  {
    if(this.mavGroups[i].material === material)
    {
      return this.mavGroups[i];
    }
  }
  
  var mva = undefined, item, attr, type;
  if(material.attributes)
  {
    mva = {};
    mva.material = material;
    mva.vas = new Mix3D.VertexAttrbs();
    mva.fvas = new Mix3D.VertexAttrbs();
    this.mavGroups.push(mva);
    for( item in material.attributes )
    {
      attr = material.attributes[item];
      type = Mix3D.VAT_Float1;
      if(attr.type === 'v2')type = Mix3D.VAT_Float2; 
      else if( attr.type === 'v3')type = Mix3D.VAT_Float3;
      else if( attr.type === 'v4')type = Mix3D.VAT_Float4;
     
      if(attr.belongTo === 'vertex')
        mva.vas.createAttrBuffer(item, type, this.vtxCount );
      else
        mva.fvas.createAttrBuffer(item, type, this.vtxCount );
    }
  }
  return mva;
};
//////////////////////////////////////////////////////////////////
})(Mix3D);// end of function(Mix3D)